#include "keyboarddevice.h"
#include "macro.h"
#include "logger.h"


#define POSTMESSAGE_NORMAL          0x00000001 /* for normal messages */
#define POSTMESSAGE_EXTENDED        0x01000001 /* for extended messages */
#define POSTMESSAGE_UP_NORMAL       0xC0000001 /* for normal keyup messages */
#define POSTMESSAGE_UP_EXTENDED     0xC1000001 /* for extended keyup messages */
#define POSTMESSAGE_ALT_LPARAM      (1 << 29)

#ifndef VK_ALT
    #define VK_ALT                      164
#endif

KeyboardDevice::KeyboardDevice()
{
    delay = 50; // 50ms default
    attachedHwnd = NULL;
	wineMode = false;
}

HWND KeyboardDevice::getAttachedHwnd()
{
    return attachedHwnd;
}

void KeyboardDevice::setAttachedInput(HWND hwnd)
{
    attachedHwnd = hwnd;
}

void KeyboardDevice::setDelay(unsigned int _delay)
{
    delay = _delay;
}

bool KeyboardDevice::keyIsExtended(unsigned int key)
{
    static unsigned int extendedList[] = { VK_INSERT, VK_DELETE, VK_HOME,
		VK_END, VK_DIVIDE, VK_LWIN, VK_RWIN, VK_PRIOR, VK_NEXT, VK_LEFT,
		VK_RIGHT, VK_UP, VK_DOWN, VK_CONTROL, VK_LCONTROL, VK_RCONTROL,
		VK_MENU, VK_LMENU, VK_RMENU, 0
    };

    for(unsigned int i = 0; extendedList[i] != 0; i++)
        if( extendedList[i] == key )
            return true;

    return false;
}

void KeyboardDevice::keyHold(unsigned int key, unsigned int modifier)
{
	if( modifier == VK_ALT ) modifier = VK_MENU;
	unsigned int modscan = modifier;
    unsigned int scan = key;

    if( MapVirtualKey(key, 0) != 0 )
        scan = MapVirtualKey(key, 0);
    if( MapVirtualKey(modifier, 0) != 0 )
        modscan = MapVirtualKey(modifier, 0);

    LPARAM lparam;
	LPARAM modlparam;
	INPUT modinp;
    INPUT inp;
	modinp.type = INPUT_KEYBOARD;
    inp.type = INPUT_KEYBOARD;

	if( keyIsExtended(modifier) ) {
        modlparam = (modscan << 16 ) | POSTMESSAGE_EXTENDED;
        modinp.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_SCANCODE;
    } else {
        modlparam = (modscan << 16 ) | POSTMESSAGE_NORMAL;
        modinp.ki.dwFlags = KEYEVENTF_SCANCODE;
	}

    if( keyIsExtended(key) ) {
        lparam = (scan << 16 ) | POSTMESSAGE_EXTENDED;
        inp.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_SCANCODE;
    } else {
        lparam = (scan << 16 ) | POSTMESSAGE_NORMAL;
        inp.ki.dwFlags = KEYEVENTF_SCANCODE;
    }

	modinp.ki.wScan = modscan;
	modinp.ki.dwExtraInfo = modlparam;
    inp.ki.wScan = scan;
    inp.ki.dwExtraInfo = lparam;


    if( attachedHwnd && !IsWindow(attachedHwnd) )
        Macro::instance()->forcedDetach();


    if( attachedHwnd ) {
		if( modifier ) {
			BYTE ks[256] = { 0 };
			GetKeyboardState(ks);
			ks[modifier] |= 0x80;
			SetKeyboardState(ks);
            PostMessage(attachedHwnd, WM_KEYDOWN, modifier, modlparam);
		}
        PostMessage(attachedHwnd, WM_KEYDOWN, key, lparam);
    } else {
		SendInput(1, &modinp, sizeof(INPUT));
        SendInput(1, &inp, sizeof(INPUT));
    }
}

void KeyboardDevice::keyRelease(unsigned int key, unsigned int modifier)
{
	if( modifier == VK_ALT ) modifier = VK_MENU;
    unsigned int modscan = modifier;
    unsigned int scan = key;

    if( MapVirtualKey(modifier, 0) != 0 )
        modscan = MapVirtualKey(modifier, 0);
    if( MapVirtualKey(key, 0) != 0 )
        scan = MapVirtualKey(key, 0);

	LPARAM modlparam;
    LPARAM lparam;
	INPUT modinp;
    INPUT inp;
	modinp.type = INPUT_KEYBOARD;
    inp.type = INPUT_KEYBOARD;


    if( keyIsExtended(modifier) ) {
        modlparam = (modscan << 16) | POSTMESSAGE_UP_EXTENDED;
        modinp.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP; }
    else {
        modlparam = (modscan << 16) | POSTMESSAGE_UP_NORMAL;
        modinp.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
    }

    if( keyIsExtended(key) ) {
        lparam = (scan << 16) | POSTMESSAGE_UP_EXTENDED;
        inp.ki.dwFlags = KEYEVENTF_EXTENDEDKEY | KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP; }
    else {
        lparam = (scan << 16) | POSTMESSAGE_UP_NORMAL;
        inp.ki.dwFlags = KEYEVENTF_SCANCODE | KEYEVENTF_KEYUP;
    }


    modinp.ki.wScan = modscan;
    modinp.ki.dwExtraInfo = modlparam;
    inp.ki.wScan = scan;
    inp.ki.dwExtraInfo = lparam;


    if( attachedHwnd && !IsWindow(attachedHwnd) )
        Macro::instance()->forcedDetach();


    if( attachedHwnd )
	{
		if( modifier ) {
			BYTE ks[256] = { 0 };
			GetKeyboardState(ks);
			ks[modifier] &= (~0x80);
			SetKeyboardState(ks);
			PostMessage(attachedHwnd, WM_KEYUP, modifier, lparam);
		}
        PostMessage(attachedHwnd, WM_KEYUP, key, lparam);
	}
    else {
        SendInput(1, &inp, sizeof(INPUT));
		SendInput(1, &modinp, sizeof(INPUT));
	}
}

void KeyboardDevice::keyPress(unsigned int key, unsigned int modifier)
{
    keyHold(key, modifier);
	Sleep(delay);
    keyRelease(key, modifier);
}

bool KeyboardDevice::keyPressed(unsigned int key)
{
	if( wineMode && keyPressedConsole(key) )
		return true;

    if( GetKeyState(key) >> 8 )
        return true;

	return false;
}

bool KeyboardDevice::keyPressedLocal(unsigned int key)
{
	HWND focus = GetForegroundWindow();

	if( focus == getAppHwnd() || focus == attachedHwnd )
	{
		if( wineMode && keyPressedConsole(key) )
			return true;

		if( GetKeyState(key) >> 8 )
			return true;

		return false;
	}
	else
		return false;
}

bool KeyboardDevice::keyPressedConsole(unsigned int key)
{
	static HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
	INPUT_RECORD buf[8];
	unsigned long count = 0;

	PeekConsoleInput(hStdin, buf, 8, &count);

	for( unsigned long i = 0; i < count; i++ )
	{
		switch( buf[i].EventType )
		{
			case KEY_EVENT: // keyboard input
				if( buf[i].Event.KeyEvent.bKeyDown )
				{
					if( key == buf[i].Event.KeyEvent.wVirtualKeyCode )
					{
						FlushConsoleInputBuffer(hStdin);
						return true;
					}
				}
			break;
		}
	}


	return false;
}

void KeyboardDevice::type(std::string message)
{ //http://www.cambiaresearch.com/c4/702b8cd1-e5b0-42e6-83ac-25f0306e3e25/Javascript-Char-Codes-Key-Codes.aspx
    for(unsigned int i = 0; i < message.length(); i++)
    {
        char thischar = message.at(i);

		int capslock = GetKeyState(0x14); // Check if caps-lock is on or off

		//char vk = VkKeyScan(thischar);
        //char keyscan = MapVirtualKey(vk, 0);

		int capitol = ( thischar >= 'A' && thischar <= 'Z' );
		int lower = ( thischar >= 'a' && thischar <= 'z' );

		short _ = VkKeyScan(thischar);
		char vk = (_ & 0xFF);
		char mods = (_ >> 8);
		char shift = mods & 1;
		char ctrl = mods & 2;
		char alt = mods & 4;

		if( capslock && (capitol || lower) )
			shift = !shift;
/*
		if( shift && ((capslock && capitol) || (!capslock && lower)) )
			shift = !shift;
*/

/*
        int capitol = ( thischar >= 'A' && thischar <= 'Z' );
        int needshift = (thischar >= '!' && thischar <= '&') ||
            (thischar >= '(' && thischar <= '+') ||
            (thischar == ':') || (thischar == '<') || (thischar == '>') ||
            (thischar == '?') || (thischar == '@') || (thischar == '^') ||
            (thischar == '_') || (thischar >= '{' && thischar <= '~');

        if( capitol || needshift )
            keyHold(VK_SHIFT);
*/
		if( !attachedHwnd )
		{
			if( shift )
				keyHold(VK_SHIFT);
			if( ctrl )
				keyHold(VK_CONTROL);
			if( alt )
				keyHold(VK_MENU);
		}

        /*
        keybd_event(vk, keyscan, 0, 0);
        Sleep(delay);
        keybd_event(vk, keyscan, KEYEVENTF_KEYUP, 0);
        Sleep(delay);
        */


		int mod_passed = shift*VK_SHIFT;

		switch(thischar)
		{
			case '\n':
				keyPress(VK_RETURN);
			break;
			case '.':
			case '>':
				keyPress(VK_OEM_PERIOD, mod_passed);
			break;
			case ',':
			case '<':
				keyPress(VK_OEM_COMMA, mod_passed);
			break;
			case '-':
			case '_':
				keyPress(VK_OEM_MINUS, mod_passed);
			break;
			case '=':
			case '+':
				keyPress(VK_OEM_PLUS, mod_passed);
			break;
			default:
				keyPress(vk, mod_passed);
			break;
		}

/*
        if( capitol || needshift )
            keyRelease(VK_SHIFT);
*/
		if( !attachedHwnd )
		{
			if( shift )
				keyRelease(VK_SHIFT);
			if( ctrl )
				keyRelease(VK_CONTROL);
			if( alt )
				keyRelease(VK_MENU);
		}
    }
}



void KeyboardDevice::clearBuffer()
{
    while( kbhit() ) getch();
}


std::string KeyboardDevice::getKeyName(unsigned int key)
{
    UINT scan = MapVirtualKey(key, 0);
    LPARAM lparam;
    if( keyIsExtended(key) )
        lparam = (scan << 16) | POSTMESSAGE_EXTENDED;
    else
        lparam = (scan << 16);
    char buf[256];

    GetKeyNameText(lparam, buf, 256);
    return std::string((char*)&buf);
}

void KeyboardDevice::setWineMode(bool enabled)
{
	wineMode = enabled;
}
